﻿using System;
using System.Collections;
using System.IO;
using Core.Enums;
using Core.Reader;
using Core.Tools;
using Core.Writer;
using Core.Entities;
using Core.ReadWriter;
using Core.ObjectOperate.ReflectionImpl;
using Core.ObjectOperate;

namespace Core.Formats
{
    /// <summary>
    /// Json 格式化器
    /// 支持 Array, Object 格式
    /// Json 格式的优点：结构清晰。
    /// 缺点：Table 格式传输非常冗余
    /// </summary>
    public sealed class JsonFormat : IDocumentFormat
    {
        private const int CodeError = 0x1;
        private const int CodeEnd = 0x2;
        private const int CodeValue = 0x3;
        

        private unsafe string UnEscape(char* LpText, int Begin, int End)
        {
            --End; // End = Right;

            if (End < Begin)
            {
                return "";
            }

            int EscapeCount = 0;

            for (int i = Begin; i < End; ++i)
            {
                if (LpText[i] == '\\')
                {
                    ++EscapeCount;

                    ++i;
                }
            }

            if (EscapeCount == 0)
            {
                return StringHelper.Substring(LpText, Begin, End + 1);
            }

            int Length = End - Begin - EscapeCount + 1;

            string Result = new string('\0', Length);

            fixed (char* LpResult = Result)
            {
                for (int i = 0; Begin <= End; ++i, ++Begin)
                {
                    if (LpText[Begin] == '\\')
                    {
                        ++Begin;
                        switch (LpText[Begin])
                        {
                            case 'n':
                                LpResult[i] = '\n';
                                break;
                            case 't':
                                LpResult[i] = '\t';
                                break;
                            case 'r':
                                LpResult[i] = '\r';
                                break;
                            default:
                                LpResult[i] = LpText[Begin];
                                break;
                        }
                    }
                    else
                    {
                        LpResult[i] = LpText[Begin];
                    }
                }
            }

            return Result;
        }

        private unsafe string UnEscape(string Text, int Begin, int End)
        {
            if (End < Begin)
            {
                return "";
            }

            fixed (char* LpText = Text)
            {
                return UnEscape(LpText, Begin, End);
            }
        }

        public unsafe string Escape(string Text)
        {
            fixed (char* LpText = Text)
            {
                int Length = Text.Length;

                for (int i = 0; i < Text.Length; ++i)
                {
                    switch (LpText[i])
                    {
                        case '\\':
                        case '\"':
                        case '\n':
                        case '\r':
                        case '\t':
                            ++Length;
                            break;
                    }
                }

                if (Length == Text.Length)
                {
                    return Text;
                }

                string Result = new string((char)0, Length);

                fixed (char* LpResult = Result)
                {
                    for (int i = 0, j = 0; i < Text.Length; ++i, ++j)
                    {
                        switch (LpText[i])
                        {
                            case '\\':
                                LpResult[j] = '\\';
                                ++j;
                                LpResult[j] = '\\';
                                break;
                            case '"':
                                LpResult[j] = '\\';
                                ++j;
                                LpResult[j] = '"';
                                break;
                            case '\n':
                                LpResult[j] = '\\';
                                ++j;
                                LpResult[j] = 'n';
                                break;
                            case '\r':
                                LpResult[j] = '\\';
                                ++j;
                                LpResult[j] = 'r';
                                break;
                            case '\t':
                                LpResult[j] = '\\';
                                ++j;
                                LpResult[j] = 't';
                                break;
                            default:
                                LpResult[j] = LpText[i];
                                break;
                        }
                    }
                }
                return Result;
            }
        }

        private unsafe void WriteEscapedText(TextWriter Writer, string Text)
        {
            if (Text.Length >= 256)
            {
                Writer.Write(Escape(Text));
            }
            else
            {
                fixed (char* LpText = Text)
                {
                    int Index = -1;

                    for (int i = 0; i < Text.Length; ++i)
                    {
                        switch (LpText[i])
                        {
                            case '\\':
                            case '"':
                            case '\n':
                            case '\r':
                            case '\t':
                                Index = i;
                                goto Break;
                        }
                    }

                    Break:
                    if (Index == -1)
                    {
                        Writer.Write(Text);
                    }
                    else
                    {
                        for (int i = 0; i < Index; ++i)
                        {
                            Writer.Write(LpText[i]);
                        }

                        while (Index < Text.Length)
                        {
                            switch (LpText[Index])
                            {
                                case '\\':
                                    Writer.Write(@"\\");
                                    break;
                                case '"':
                                    Writer.Write("\\\"");
                                    break;
                                case '\n':
                                    Writer.Write(@"\n");
                                    break;
                                case '\r':
                                    Writer.Write(@"\r");
                                    break;
                                case '\t':
                                    Writer.Write(@"\t");
                                    break;
                                default:
                                    Writer.Write(LpText[Index]);
                                    break;
                            }

                            ++Index;
                        }
                    }
                }
            }
        }

        public unsafe string UnEscape(string Text)
        {
            fixed (char* LpText = Text)
            {
                int EscapeCount = 0;

                for (int i = 0; i < Text.Length; ++i)
                {
                    if (LpText[i] == '\\')
                    {
                        ++EscapeCount;

                        ++i;
                    }
                }

                if (EscapeCount == 0)
                {
                    return Text;
                }

                int Length = Text.Length - EscapeCount;

                string Result = new string('\0', Length);

                fixed (char* LpResult = Result)
                {
                    for (int i = 0, j = 0; i < Text.Length; ++j, ++i)
                    {
                        if (LpText[i] == '\\')
                        {
                            ++i;
                            switch (LpText[i])
                            {
                                case 'n':
                                    LpResult[j] = '\n';
                                    break;
                                case 't':
                                    LpResult[j] = '\t';
                                    break;
                                case 'r':
                                    LpResult[j] = '\r';
                                    break;
                                default:
                                    LpResult[j] = LpText[i];
                                    break;
                            }
                        }
                        else
                        {
                            LpResult[j] = LpText[i];
                        }
                    }
                }

                return Result;
            }
        }

        public void Encode(object Value, TextWriter Writer)
        {
            if (!WriteStringValue(Writer, Value))
            {
                if (Value is IDataReader)
                {
                    Encode((IDataReader)Value, Writer);
                }
                else if (Value is IList)
                {
                    Encode((IList)Value, Writer);
                }
                else if (Value is IDictionary)
                {
                    Encode((IDictionary)Value, Writer);
                }
                else
                {
                    Encode(ObjectOperater.Create(Value.GetType()), Value, Writer);
                }
            }
        }

        public void Encode(IList Collection, TextWriter Writer)
        {
            Writer.Write('[');

            var Enumerator = Collection.GetEnumerator();

            if (Enumerator.MoveNext())
            {
                Encode(Enumerator.Current, Writer);

                while (Enumerator.MoveNext())
                {
                    Writer.Write(',');

                    Encode(Enumerator.Current, Writer);
                }
            }

            Writer.Write(']');
        }

        public void Encode(IObjectOperater Operater, object Object, TextWriter Writer)
        {
            Writer.Write('{');

            if (Operater.PropertiesCount >= 1)
            {
                Writer.Write('"');

                Writer.Write(Operater.GetPropertyName(0));

                Writer.Write("\":");

                Encode(Operater.GetPropertyValue(Object, 0), Writer);

                for (int i = 1; i < Operater.PropertiesCount; i++)
                {
                    Writer.Write(",\"");

                    Writer.Write(Operater.GetPropertyName(i));

                    Writer.Write("\":");

                    Encode(Operater.GetPropertyValue(Object, i), Writer);
                }
            }

            Writer.Write('}');
        }

        public void Encode(IDictionary Object, TextWriter Writer)
        {
            Writer.Write('{');

            var Enumerator = Object.GetEnumerator();

            if (Enumerator.MoveNext())
            {
                Writer.Write('"');


                WriteEscapedText(Writer, Enumerator.Key.ToString());

                Writer.Write("\":");

                Encode(Enumerator.Value, Writer);

                while (Enumerator.MoveNext())
                {
                    Writer.Write(",\"");

                    WriteEscapedText(Writer, Enumerator.Key.ToString());

                    Writer.Write("\":");

                    Encode(Enumerator.Value, Writer);
                }
            }

            Writer.Write('}');
        }

        public void Encode(IDataReader Reader, TextWriter Writer)
        {
            if (Reader.ObjectType == RWTypes.Array)
            {
                Writer.Write('[');

                if (Reader.Read())
                {
                    if (Reader.Count >= 1)
                    {
                        Encode(Reader[0], Writer);

                        for (int i = 1; i < Reader.Count; i++)
                        {
                            Writer.Write(',');

                            Encode(Reader[i], Writer);
                        }
                    }
                }

                Writer.Write(']');
            }
            else
            {
                if (Reader.ObjectType == RWTypes.Table)
                {
                    Writer.Write('[');
                }

                if (Reader.Read())
                {
                    RowLoop:
                    Writer.Write('{');

                    var Fields = Reader.Fields.GetEnumerator();

                    int FieldIndex = 0;

                    if (Fields.MoveNext())
                    {
                        ColumnLoop:

                        Writer.Write('"');

                        WriteEscapedText(Writer, Fields.Current);

                        Writer.Write("\":");

                        Encode(Reader[FieldIndex], Writer);

                        ++FieldIndex;

                        if (Fields.MoveNext())
                        {
                            Writer.Write(',');

                            goto ColumnLoop;
                        }
                    }

                    Writer.Write('}');

                    if (Reader.Read())
                    {
                        Writer.Write(',');

                        goto RowLoop;
                    }
                }

                if (Reader.ObjectType == RWTypes.Table)
                {
                    Writer.Write(']');
                }
            }
        }

        public string Encode(object Value)
        {
            using (var Writer = new StringWriter())
            {
                Encode(Value, Writer);

                return Writer.ToString();
            }
        }

        public string Encode(IList Value)
        {
            using (var Writer = new StringWriter())
            {
                Encode(Value, Writer);

                return Writer.ToString();
            }
        }

        public string Encode(IDictionary Value)
        {
            using (var Writer = new StringWriter())
            {
                Encode(Value, Writer);

                return Writer.ToString();
            }
        }

        public string Encode(IDataReader Value)
        {
            using (var Writer = new StringWriter())
            {
                Encode(Value, Writer);

                return Writer.ToString();
            }
        }

        public unsafe void Decode(string Text, IDataWriter Writer)
        {
            if (Text != null && Text.Length != 0)
            {
                int Begin = 0;

                fixed (char* LpText = Text)
                {
                    Decode(LpText, ref Begin, Text.Length - 1, Writer);
                }
            }
            else
            {
                throw new NullReferenceException("Text");
            }
        }

        public T Decode<T>(string Text)
        {
            return (T)Decode(Text, typeof(T));
        }

        public object Decode(string Text, Type Type)
        {
            var Writer = BaseReadWriter.Create(Type);

            Decode(Text, Writer);

            return Writer.Content;
        }

        public void Decode(string Text, object Content)
        {
            Decode(Text, BaseReadWriter.Create(Content));
        }

        public void Decode(string Text, IList Array)
        {
            Decode(Text, new ListReadWriter(Array));
        }

        public void Decode(string Text, IDictionary Object)
        {
            Decode(Text, new DictionaryReadWriter(Object));
        }

        private object GetTextObject(string Text)
        {
            return Text;
        }

        private unsafe void Decode(char* LpText, ref int Begin, int End, IDataWriter Writer)
        {
            if (LpText[Begin] == '{')
            {
                DecodeObject(LpText, ref Begin, End, Writer);
            }
            else if (LpText[Begin] == '[')
            {
                DecodeArray(LpText, ref Begin, End, Writer);
            }
        }

        private unsafe int ReadValue(char* LpText, char EndChar, ref int Begin, int End, out object Value, Func<CreateChildrenWriterInfo, IDataWriter> CreateWriter)
        {
            int Flag;

            string StringValue;

            Value = null;

            for (++Begin; Begin <= End; ++Begin)
            {
                char Char = LpText[Begin];

                if (Char != ' ' && Char != '\n' && Char != '\r' && Char != '\t')
                {
                    break;
                }
            }

            if (Begin > End)
            {
                return CodeError;
            }

            if (LpText[Begin] == EndChar)
            {
                return CodeEnd;
            }

            switch (LpText[Begin])
            {
                case '{':
                    var CCWInfo = new CreateChildrenWriterInfo();

                    CCWInfo.ObjectType = RWTypes.Object;

                    var OutWriter = CreateWriter(CCWInfo);

                    DecodeObject(LpText, ref Begin, End, OutWriter);

                    Value = OutWriter;

                    return CodeValue;
                case '[':
                    CCWInfo = new CreateChildrenWriterInfo();

                    CCWInfo.ObjectType = RWTypes.Array;

                    OutWriter = CreateWriter(CCWInfo);

                    DecodeArray(LpText, ref Begin, End, OutWriter);

                    Value = OutWriter;

                    return CodeValue;
                case '"':

                    ++Begin;

                    Flag = StringHelper.NoEscapeIndexOf(LpText, '"', Begin, End);

                    StringDecode:

                    StringValue = UnEscape(LpText, Begin, Flag);

                    Value = GetTextObject(StringValue);

                    Begin = Flag;

                    return CodeValue;
                case '\'':

                    ++Begin;

                    Flag = StringHelper.NoEscapeIndexOf(LpText, '\'', Begin, End);

                    goto StringDecode;
            }

            Flag = Begin;

            while (Flag <= End && LpText[Flag] != ',' && LpText[Flag] != EndChar)
            {
                ++Flag;
            }

            StringValue = StringHelper.Trim(LpText, Begin, Flag);

            Begin = Flag - 1;

            switch (StringValue)
            {
                case "undefined":
                case "null":
                    Value = null;
                    return CodeValue;
                case "true":
                    Value = true;
                    return CodeValue;
                case "false":
                    Value = false;
                    return CodeValue;
                default:
                    bool IsNegative;
                    bool IsFloat;
                    int Digit;
                    int Float;

                    if (NumberHelper.IsNumber(StringValue, 10, out IsNegative, out IsFloat, out Digit, out Float))
                    {
                        if (IsFloat)
                        {
                            if (Digit + Float <= 15)
                            {
                                Value = double.Parse(StringValue);
                            }
                            else if (Digit + Float <= 28)
                            {
                                Value = decimal.Parse(StringValue);
                            }
                            else
                            {
                                Value = double.Parse(StringValue);
                            }
                        }
                        else if (Digit <= 9)
                        {
                            Value = int.Parse(StringValue);
                        }
                        else if (Digit <= 18)
                        {
                            Value = long.Parse(StringValue);
                        }
                        else if (Digit <= 28)
                        {
                            Value = decimal.Parse(StringValue);
                        }
                        else
                        {
                            Value = double.Parse(StringValue);
                        }

                        return CodeValue;
                    }

                    throw new FormatException("Json Format Error : " + StringValue);
            }
        }

        private unsafe void DecodeArray(char* LpText, ref int Begin, int End, IDataWriter Writer)
        {
            for (; Begin <= End; ++Begin)
            {
                switch (LpText[Begin])
                {
                    case ' ':
                    case '\n':
                    case '\r':
                    case '\t':
                        continue;
                    case '[':

                        Writer.ObjectType = RWTypes.Array;

                        Writer.Next();
                        goto case ',';
                    case ']':
                        goto ReturnValue;
                    case ',':
                        object Value;

                        switch (ReadValue(LpText, ']', ref Begin, End, out Value, (Info) =>
                         {
                             return Writer.CreateChildrenWriter(Info);
                         }))
                        {
                            case CodeValue:
                                Writer.Add(null, Value);
                                continue;
                            case CodeEnd:
                                goto ReturnValue;
                            default:
                                goto FormatError;
                        }
                    default:
                        goto FormatError;
                }
            }

            FormatError:

            throw new FormatException("Text");
            ReturnValue:
            return;
        }

        private unsafe void DecodeObject(char* LpText, ref int Begin, int End, IDataWriter Writer)
        {
            int Flag;

            for (; Begin <= End; ++Begin)
            {
                switch (LpText[Begin])
                {
                    case ' ':
                    case '\n':
                    case '\r':
                    case '\t':
                        continue;
                    case '{':

                        Writer.ObjectType = RWTypes.Object;

                        Writer.Next();

                        goto case ',';
                    case '}':
                        goto ReturnValue;
                    case ',':

                        char Char = '\0';

                        for (++Begin; Begin <= End; ++Begin)
                        {
                            Char = LpText[Begin];

                            if (Char != ' ' && Char != '\n' && Char != '\r' && Char != '\t')
                            {
                                break;
                            }
                        }

                        if (Begin > End)
                        {
                            goto FormatError;
                        }

                        if (Char == '}')
                        {
                            goto case '}';
                        }

                        string Name;

                        if (Char != '"' && Char != '\'')
                        {
                            Flag = StringHelper.IndexOf(LpText, ':', Begin, End);

                            Name = StringHelper.Trim(LpText, Begin, Flag);

                            Begin = Flag;
                        }
                        else
                        {
                            ++Begin;

                            Flag = StringHelper.NoEscapeIndexOf(LpText, Char, Begin, End);

                            Name = StringHelper.Substring(LpText, Begin, Flag);

                            Begin = StringHelper.IndexOf(LpText, ':', Flag + 1, End);
                        }

                        object Value;

                        switch (ReadValue(LpText, '}', ref Begin, End, out Value, (Info) =>
                         {
                             Info.Name = Name;
                             return Writer.CreateChildrenWriter(Info);
                         }))
                        {
                            case CodeValue:
                                Writer[Name] = Value;
                                continue;
                            case CodeEnd:
                                goto ReturnValue;
                            default:
                                goto FormatError;
                        }

                    default:
                        goto FormatError;
                }
            }

            FormatError:

            throw new FormatException("Text");
            ReturnValue:
            return;
        }

        public object ParseValue(string Text)
        {
            if (Text == null || Text.Length == 0)
            {
                return null;
            }

            switch (Text)
            {
                case "null":
                    return null;
                case "undefined":
                    // TODO undefine
                    return null;
                case "true":
                    return true;
                case "false":
                    return false;
            }

            int Right = Text.Length - 1;

            if (Text.Length >= 2 && ((Text[0] == '\"' && Text[Right] == '\"') || (Text[0] == '\'' && Text[Right] == '\'')))
            {
                string StringValue = UnEscape(Text, 1, Right);

                return GetTextObject(StringValue);
            }

            bool IsNegative;
            bool IsFloat;
            int Digit;
            int Float;

            if (NumberHelper.IsNumber(Text, 10, out IsNegative, out IsFloat, out Digit, out Float))
            {
                if (IsFloat)
                {
                    if (Digit + Float <= 15)
                    {
                        return double.Parse(Text);
                    }
                    else if (Digit + Float <= 28)
                    {
                        return decimal.Parse(Text);
                    }
                }
                else if (Digit <= 9)
                {
                    return int.Parse(Text);
                }
                else if (Digit <= 18)
                {
                    return long.Parse(Text);
                }
                else if (Digit <= 28)
                {
                    return decimal.Parse(Text);
                }

                return double.Parse(Text);
            }

            throw new FormatException("Text");
        }

        private bool WriteStringValue(TextWriter Writer, object Value)
        {
            if (Value == null)
            {
                Writer.Write("null");

                return true;
            }

            if (Value is IConvertible)
            {
                switch (((IConvertible)Value).GetTypeCode())
                {
                    case TypeCode.Boolean:
                        Writer.Write((bool)Value ? "true" : "false");
                        return true;
                    case TypeCode.Byte:
                        Writer.Write(((byte)Value).ToString());
                        return true;
                    case TypeCode.Char:
                        Writer.Write('"');
                        Writer.Write(((char)Value));
                        Writer.Write('"');
                        return true;
                    case TypeCode.DBNull:
                    case TypeCode.Empty:
                        Writer.Write("null");
                        return true;
                    case TypeCode.DateTime:
                        Writer.Write('"');
                        Writer.Write(((DateTime)Value).ToString("yyyy-MM-dd HH:mm:ss"));
                        Writer.Write('"');
                        return true;
                    case TypeCode.Decimal:
                        Writer.Write(((decimal)Value).ToString());
                        return true;
                    case TypeCode.Double:
                        Writer.Write(((double)Value).ToString());
                        return true;
                    case TypeCode.Int16:
                        Writer.Write(((short)Value).ToString());
                        return true;
                    case TypeCode.Int32:
                        Writer.Write(((int)Value).ToString());
                        return true;
                    case TypeCode.Int64:
                        Writer.Write(((long)Value).ToString());
                        return true;
                    case TypeCode.SByte:
                        Writer.Write(((sbyte)Value).ToString());
                        return true;
                    case TypeCode.Single:
                        Writer.Write(((float)Value).ToString());
                        return true;
                    case TypeCode.String:
                        Writer.Write('"');
                        WriteEscapedText(Writer, (string)Value);
                        Writer.Write('"');
                        return true;
                    case TypeCode.UInt16:
                        Writer.Write(((ushort)Value).ToString());
                        return true;
                    case TypeCode.UInt32:
                        Writer.Write(((uint)Value).ToString());
                        return true;
                    case TypeCode.UInt64:
                        Writer.Write(((ulong)Value).ToString());
                        return true;
                }
            }
            else if (Value is Guid)
            {
                Writer.Write('"');
                Writer.Write(((Guid)Value).ToString("N"));
                Writer.Write('"');

                return true;
            }

            return false;
        }

        public string GetString(object Value)
        {
            if (Value == null)
            {
                return "null";
            }

            if (Value is IConvertible)
            {
                switch (((IConvertible)Value).GetTypeCode())
                {
                    case TypeCode.Boolean:
                        return (bool)Value ? "true" : "false";
                    case TypeCode.Byte:
                        return ((byte)Value).ToString();
                    case TypeCode.Char:
                        return "\"" + ((char)Value) + "\"";
                    case TypeCode.DBNull:
                    case TypeCode.Empty:
                        return "null";
                    case TypeCode.DateTime:
                        return "\"" + ((DateTime)Value).ToString("yyyy-MM-dd HH:mm:ss") + "\"";
                    case TypeCode.Decimal:
                        return ((decimal)Value).ToString();
                    case TypeCode.Double:
                        return ((double)Value).ToString();
                    case TypeCode.Int16:
                        return ((short)Value).ToString();
                    case TypeCode.Int32:
                        return ((int)Value).ToString();
                    case TypeCode.Int64:
                        return ((long)Value).ToString();
                    case TypeCode.SByte:
                        return ((sbyte)Value).ToString();
                    case TypeCode.Single:
                        return ((float)Value).ToString();
                    case TypeCode.String:
                        return "\"" + Escape((string)Value) + "\"";
                    case TypeCode.UInt16:
                        return ((ushort)Value).ToString();
                    case TypeCode.UInt32:
                        return ((uint)Value).ToString();
                    case TypeCode.UInt64:
                        return ((ulong)Value).ToString();
                }
            }
            else if (Value is Guid)
            {
                return "\"" + ((Guid)Value).ToString("N") + "\"";
            }

            return null;
        }
    }
}